/** @file
This Driver will conditionally produce CsmLoader protocol, LegacyBiosPlatform driver depends on that.

  Currently, we have below CSM/Non-CSM version driver:
  +-------+------------+--------------------+------------------------+
  | Driver|   CSM      |     Native         |      Selection         |
  +-------+------------+--------------------+------------------------+
  |Network| 0x03       | ???                | BDS dispatch OROM direc|
  |       | LegacyBios |                    | No need CSM thunk      |
  +-------+------------+--------------------+------------------------+
  |  GOP  | 0x03       | ???                | PlatformDriverOverride |
  |       | LegacyBios |                    |                        |
  +-------+------------+--------------------+------------------------+
  | RAID  | 0x11+0x10  | ???                | PlatformDriverOverride |
  |       | LegacyBios |                    |                        |
  +-------+------------+--------------------+------------------------+
  |  USB  | ???        | 0x30               | Version order          |
  |       | LegacyBios |                    |                        |
  +-------+------------+--------------------+------------------------+
  | Ps2Kb | 0x03       | 0x0A               | PlatformDriverOverride |
  |       | LegacyBios |                    |                        |
  +-------+------------+--------------------+------------------------+

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2010 - 2016 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains a 'Sample Driver' and is licensed as such under the terms
  of your license agreement with Intel or your vendor. This file may be modified
  by the user, subject to the additional terms of the license agreement.

@par Specification Reference:
**/

#include <PiDxe.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/PciLib.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/PcdLib.h>
#include <Protocol/DevicePath.h>
#include <Protocol/ComponentName.h>
#include <Protocol/ComponentName2.h>
#include <Protocol/DriverBinding.h>
#include <Protocol/PlatformDriverOverride.h>
#include <Protocol/LegacyBios.h>
#include <IndustryStandard/Pci.h>

#include <SetupVariable.h>

extern EFI_GUID                                     gCsmLoaderProtocolGuid;

EFI_STATUS
EFIAPI
PlatformGetDriver (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL          *This,
  IN     EFI_HANDLE                                 ControllerHandle,
  IN OUT EFI_HANDLE                                 *DriverImageHandle
  );

EFI_STATUS
EFIAPI
PlatformGetDriverPath (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL          *This,
  IN     EFI_HANDLE                                 ControllerHandle,
  IN OUT EFI_DEVICE_PATH_PROTOCOL                   **DriverImagePath
  );

EFI_STATUS
EFIAPI
PlatformDriverLoaded (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL          *This,
  IN EFI_HANDLE                                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL                       *DriverImagePath,
  IN EFI_HANDLE                                     DriverImageHandle
  );

///
/// This protocol matches one or more drivers to a controller. A platform driver
/// produces this protocol, and it is installed on a separate handle. This protocol
/// is used by the ConnectController() boot service to select the best driver
/// for a controller. All of the drivers returned by this protocol have a higher
/// precedence than drivers found from an EFI Bus Specific Driver Override Protocol
/// or drivers found from the general UEFI driver Binding search algorithm. If more
/// than one driver is returned by this protocol, then the drivers are returned in
/// order from highest precedence to lowest precedence.
///
EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL mPlatformOverride = {
  PlatformGetDriver,
  PlatformGetDriverPath,
  PlatformDriverLoaded
};

//
// Precise Controller/Driver mapping
//
#define gPciRootBridge \
  { \
    { \
      ACPI_DEVICE_PATH, ACPI_DP, \
      { \
        (UINT8) (sizeof (ACPI_HID_DEVICE_PATH)), \
        (UINT8) ((sizeof (ACPI_HID_DEVICE_PATH)) >> 8) \
      } \
    }, EISA_PNP_ID (0x0A03), 0 \
  }

#define gEndEntire \
  { \
    END_DEVICE_PATH_TYPE, END_ENTIRE_DEVICE_PATH_SUBTYPE, { END_DEVICE_PATH_LENGTH, 0 } \
  }

typedef struct {
  ACPI_HID_DEVICE_PATH      PciRootBridge;
  PCI_DEVICE_PATH           IsaBridge;
  ACPI_HID_DEVICE_PATH      Keyboard;
  EFI_DEVICE_PATH_PROTOCOL  End;
} PLATFORM_KEYBOARD_DEVICE_PATH;

typedef struct {
  ACPI_HID_DEVICE_PATH      PciRootBridge;
  PCI_DEVICE_PATH           PciDevice;
  EFI_DEVICE_PATH_PROTOCOL  End;
} PLATFORM_ONBOARD_CONTROLLER_DEVICE_PATH;

PLATFORM_KEYBOARD_DEVICE_PATH     gKeyboardDevicePath = {
  gPciRootBridge,
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    0,
    0x1f
  },
  {
    {
      ACPI_DEVICE_PATH,
      ACPI_DP,
      {
        (UINT8) (sizeof (ACPI_HID_DEVICE_PATH)),
        (UINT8) ((sizeof (ACPI_HID_DEVICE_PATH)) >> 8)
      }
    },
    EISA_PNP_ID(0x0303),
    0
  },
  gEndEntire
};

PLATFORM_ONBOARD_CONTROLLER_DEVICE_PATH         gPlatformOnboardVgaDevice = {
  gPciRootBridge,
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    0x0,
    0x2
  },
  gEndEntire
};

PLATFORM_ONBOARD_CONTROLLER_DEVICE_PATH         gPlatformOnboardSataAhciDevice = {
  gPciRootBridge,
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_PCI_DP,
      {
        (UINT8) (sizeof (PCI_DEVICE_PATH)),
        (UINT8) ((sizeof (PCI_DEVICE_PATH)) >> 8)
      }
    },
    0x0,
    0x17
  },
  gEndEntire
};

typedef struct {
  EFI_DEVICE_PATH_PROTOCOL   *DevicePath;
  CHAR16                     *DriverName;
  EFI_HANDLE                 DriverHandle;
} PRECISE_CONTROLLER_DRIVER_MAPPING;

GLOBAL_REMOVE_IF_UNREFERENCED PRECISE_CONTROLLER_DRIVER_MAPPING  mPreciseControllerDriverMappingBasic[] = {
  // Video - Acpi(PNP0A03, 0)/Pci(2|0)
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardVgaDevice,        L"BIOS[INT10] Video Driver"},
};

GLOBAL_REMOVE_IF_UNREFERENCED PRECISE_CONTROLLER_DRIVER_MAPPING  mPreciseControllerDriverMappingRaid[] = {
  // Video - Acpi(PNP0A03, 0)/Pci(2|0)
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardVgaDevice,        L"BIOS[INT10] Video Driver"},
  // Raid - Acpi(PNP0A03, 0)/Pci(1F|2)
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardSataAhciDevice,   L"PCH Serial ATA Controller Initialization Driver"},
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardSataAhciDevice,   L"CSM Intel Raid AtaAtapiPassThru Driver"},
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardSataAhciDevice,   L"BIOS[INT13] Intel RAID Block Io Driver"},
  {(EFI_DEVICE_PATH_PROTOCOL *)&gPlatformOnboardSataAhciDevice,   L"SCSI Bus Driver"},
};

PRECISE_CONTROLLER_DRIVER_MAPPING  *mPreciseControllerDriverMapping;
UINTN                              mPreciseControllerDriverMappingCount;

VOID
AssignDriverHandleByName (
  IN EFI_HANDLE                DriverHandle,
  IN CHAR16                    *DriverName
  )
{
  UINTN                                      Index;

  for (Index = 0; Index < mPreciseControllerDriverMappingCount; Index++) {
    if (StrCmp (mPreciseControllerDriverMapping[Index].DriverName, DriverName) == 0) {
      mPreciseControllerDriverMapping[Index].DriverHandle = DriverHandle;
      return ;
    }
  }
}

BOOLEAN
InitializePreciseControllerDriverMapping (
  VOID
  )
{
  UINTN                                      Index;
  EFI_STATUS                                 Status;
  UINTN                                      DriverBindingHandleCount;
  EFI_HANDLE                                 *DriverBindingHandleBuffer;
  EFI_COMPONENT_NAME2_PROTOCOL               *ComponentName2;
  EFI_COMPONENT_NAME_PROTOCOL                *ComponentName;
  CHAR16                                     *DriverName;
  BOOLEAN                                    ReturnValue;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiDriverBindingProtocolGuid,
                  NULL,
                  &DriverBindingHandleCount,
                  &DriverBindingHandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  for (Index = 0; Index < DriverBindingHandleCount; Index++) {
    Status = gBS->HandleProtocol (
                    DriverBindingHandleBuffer[Index],
                    &gEfiComponentName2ProtocolGuid,
                    (VOID **) &ComponentName2
                    );
    if (!EFI_ERROR (Status)) {
      Status = ComponentName2->GetDriverName (
                                 ComponentName2,
                                 "eng",
                                 &DriverName
                                 );
      if (!EFI_ERROR (Status)) {
        AssignDriverHandleByName (DriverBindingHandleBuffer[Index], DriverName);
        continue ;
      }
    }
    Status = gBS->HandleProtocol (
                    DriverBindingHandleBuffer[Index],
                    &gEfiComponentNameProtocolGuid,
                    (VOID **) &ComponentName
                    );
    if (!EFI_ERROR (Status)) {
      Status = ComponentName->GetDriverName (
                                ComponentName,
                                "eng",
                                &DriverName
                                );
      if (!EFI_ERROR (Status)) {
        AssignDriverHandleByName (DriverBindingHandleBuffer[Index], DriverName);
        continue ;
      }
    }
  }

  ReturnValue = TRUE;
  for (Index = 0; Index < mPreciseControllerDriverMappingCount; Index++) {
    if (mPreciseControllerDriverMapping[Index].DriverHandle == NULL) {
      DEBUG ((DEBUG_ERROR, "InitializePreciseControllerDriverMapping - %s not found\n", mPreciseControllerDriverMapping[Index].DriverName));
      ReturnValue = FALSE;
    }
  }

  return ReturnValue;
}

/**
  Compare two device pathes to check if they are exactly same.

  @param DevicePath1    A pointer to the first device path data structure.
  @param DevicePath2    A pointer to the second device path data structure.

  @retval TRUE    They are same.
  @retval FALSE   They are not same.

**/
BOOLEAN
EfiCompareDevicePath (
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath1,
  IN EFI_DEVICE_PATH_PROTOCOL *DevicePath2
  )
{
  UINTN Size1;
  UINTN Size2;

  Size1 = GetDevicePathSize (DevicePath1);
  Size2 = GetDevicePathSize (DevicePath2);

  if (Size1 != Size2) {
    return FALSE;
  }

  if (CompareMem (DevicePath1, DevicePath2, Size1) != 0) {
    return FALSE;
  }

  return TRUE;
}

EFI_STATUS
PlatformPreciseGetDriverViaController (
  IN EFI_HANDLE                 ControllerHandle,
  IN OUT EFI_HANDLE             *DriverImageHandle
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  UINTN                     Index;
  BOOLEAN                   DriverImageHandleFound;
  BOOLEAN                   ControllerHandleFound;
  static BOOLEAN            PreciseControllerDriverMappingInitialized = FALSE;

  if (!PreciseControllerDriverMappingInitialized) {
    PreciseControllerDriverMappingInitialized = InitializePreciseControllerDriverMapping ();
  }

  Status = gBS->HandleProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **)&DevicePath
                  );
  if (EFI_ERROR (Status)) {
    *DriverImageHandle = NULL;
    return EFI_NOT_FOUND;
  }

  DriverImageHandleFound = FALSE;
  ControllerHandleFound  = FALSE;
  for (Index = 0; Index < mPreciseControllerDriverMappingCount; Index++) {
    if (EfiCompareDevicePath (mPreciseControllerDriverMapping[Index].DevicePath, DevicePath)) {
      ControllerHandleFound = TRUE;
      if (*DriverImageHandle == NULL) {
        //
        // return first one
        //
        *DriverImageHandle = mPreciseControllerDriverMapping[Index].DriverHandle;

        return EFI_SUCCESS;
      } else {
        //
        // return next one
        //
        if (DriverImageHandleFound) {
          //
          // It is found previously, return now.
          //
          *DriverImageHandle = mPreciseControllerDriverMapping[Index].DriverHandle;

          return EFI_SUCCESS;
        } else {
          if (*DriverImageHandle == mPreciseControllerDriverMapping[Index].DriverHandle) {
            DriverImageHandleFound = TRUE;
          }
        }
      }
    }
  }

  *DriverImageHandle = NULL;

  if (ControllerHandleFound && (!DriverImageHandleFound)) {
    return EFI_INVALID_PARAMETER;
  } else {
    return EFI_NOT_FOUND;
  }
}


//
// Platform Override
//

/**
  Retrieves the image handle of the platform override driver for a controller in the system.

  @param  This                  A pointer to the EFI_PLATFORM_DRIVER_OVERRIDE_
                                PROTOCOL instance.
  @param  ControllerHandle      The device handle of the controller to check if a driver override
                                exists.
  @param  DriverImageHandle     On input, a pointer to the previous driver image handle returned
                                by GetDriver(). On output, a pointer to the next driver
                                image handle.

  @retval EFI_SUCCESS           The driver override for ControllerHandle was returned in
                                DriverImageHandle.
  @retval EFI_NOT_FOUND         A driver override for ControllerHandle was not found.
  @retval EFI_INVALID_PARAMETER The handle specified by ControllerHandle is not a valid handle.
  @retval EFI_INVALID_PARAMETER DriverImageHandle is not a handle that was returned on a
                                previous call to GetDriver().

**/
EFI_STATUS
EFIAPI
PlatformGetDriver (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN     EFI_HANDLE                                     ControllerHandle,
  IN OUT EFI_HANDLE                                     *DriverImageHandle
  )
{
  if (DriverImageHandle == NULL || ControllerHandle == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  return PlatformPreciseGetDriverViaController (ControllerHandle, DriverImageHandle);
}

/**
  Retrieves the device path of the platform override driver for a controller in the system.

  @param  This                  A pointer to the EFI_PLATFORM_DRIVER_OVERRIDE_
                                PROTOCOL instance.
  @param  ControllerHandle      The device handle of the controller to check if a driver override
                                exists.
  @param  DriverImagePath       On input, a pointer to the previous driver device path returned by
                                GetDriverPath(). On output, a pointer to the next driver
                                device path. Passing in a pointer to NULL will return the first
                                driver device path for ControllerHandle.

  @retval EFI_SUCCESS           The driver override for ControllerHandle was returned in
                                DriverImageHandle.
  @retval EFI_UNSUPPORTED       The operation is not supported.
  @retval EFI_NOT_FOUND         A driver override for ControllerHandle was not found.
  @retval EFI_INVALID_PARAMETER The handle specified by ControllerHandle is not a valid handle.
  @retval EFI_INVALID_PARAMETER DriverImagePath is not a device path that was returned on a
                                previous call to GetDriverPath().

**/
EFI_STATUS
EFIAPI
PlatformGetDriverPath (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL              *This,
  IN     EFI_HANDLE                                     ControllerHandle,
  IN OUT EFI_DEVICE_PATH_PROTOCOL                       **DriverImagePath
  )
{
  return EFI_UNSUPPORTED;
}

/**
  Used to associate a driver image handle with a device path that was returned on a prior call to the
  GetDriverPath() service. This driver image handle will then be available through the
  GetDriver() service.

  @param  This                  A pointer to the EFI_PLATFORM_DRIVER_OVERRIDE_
                                PROTOCOL instance.
  @param  ControllerHandle      The device handle of the controller.
  @param  DriverImagePath       A pointer to the driver device path that was returned in a prior
                                call to GetDriverPath().
  @param  DriverImageHandle     The driver image handle that was returned by LoadImage()
                                when the driver specified by DriverImagePath was loaded
                                into memory.

  @retval EFI_SUCCESS           The association between DriverImagePath and
                                DriverImageHandle was established for the controller specified
                                by ControllerHandle.
  @retval EFI_UNSUPPORTED       The operation is not supported.
  @retval EFI_NOT_FOUND         DriverImagePath is not a device path that was returned on a prior
                                call to GetDriverPath() for the controller specified by
                                ControllerHandle.
  @retval EFI_INVALID_PARAMETER ControllerHandle is not a valid device handle.
  @retval EFI_INVALID_PARAMETER DriverImagePath is not a valid device path.
  @retval EFI_INVALID_PARAMETER DriverImageHandle is not a valid image handle.

**/
EFI_STATUS
EFIAPI
PlatformDriverLoaded (
  IN EFI_PLATFORM_DRIVER_OVERRIDE_PROTOCOL          *This,
  IN EFI_HANDLE                                     ControllerHandle,
  IN EFI_DEVICE_PATH_PROTOCOL                       *DriverImagePath,
  IN EFI_HANDLE                                     DriverImageHandle
  )
{
  return EFI_UNSUPPORTED;
}

/**
  Install Driver to produce Legacy BIOS protocol.

    ImageHandle - Handle of image
    SystemTable - Pointer to System Table

  EFI_SUCCESS - Legacy Bios Platform protocol installed
  Other       - No protocol installed, unload driver.

**/
EFI_STATUS
EFIAPI
CsmLoaderEntrypoint (
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS              Status;
  EFI_HANDLE              Handle;
  UINTN                   VariableSize;
  PCH_SETUP               PchSetup;
  BOOLEAN                 LoadControllerDriverMappingRaidTable;

  LoadControllerDriverMappingRaidTable = FALSE;

  VariableSize = sizeof (PCH_SETUP);
  Status = gRT->GetVariable (
                  L"PchSetup",
                  &gPchSetupVariableGuid,
                  NULL,
                  &VariableSize,
                  &PchSetup
                  );
  if (EFI_ERROR(Status)) {
    PchSetup.SataInterfaceMode = SATA_MODE_AHCI;
  }

  if ((PciRead8 (PCI_LIB_ADDRESS(0, 0x1F, 2, 0xB)) == PCI_CLASS_MASS_STORAGE) &&
      (PciRead8 (PCI_LIB_ADDRESS(0, 0x1F, 2, 0xA)) == PCI_CLASS_MASS_STORAGE_RAID)) {
    //
    // When CSM-Support is enabled and SATA controller (D31:F2) is in ACHI mode,
    // it should be served by AHCI EFI Driver
    //
    if (PcdGetBool (PcdCsmEnable) && (PchSetup.SataInterfaceMode == SATA_MODE_AHCI)) {
      LoadControllerDriverMappingRaidTable = TRUE;
    }
  }
  //
  // When CSM-Support is enabled and Legacy OROM is disabled,
  // SATA controller (D31:F2) should be served by RST EFI Driver.
  // When CSM-Support is disabled, SATA controller (D31:F2) should be served by RST EFI Driver.
  //

  if (LoadControllerDriverMappingRaidTable) {
    //
    // CSM RAID Driver needs CSM RAID controller driver mapping table
    //
    mPreciseControllerDriverMapping = mPreciseControllerDriverMappingRaid;
    mPreciseControllerDriverMappingCount = sizeof(mPreciseControllerDriverMappingRaid)/sizeof(mPreciseControllerDriverMappingRaid[0]);
  } else {
    //
    // Other device or EFI RST driver don't need CSM RAID controller driver mapping table
    //
    mPreciseControllerDriverMapping = mPreciseControllerDriverMappingBasic;
    mPreciseControllerDriverMappingCount = sizeof(mPreciseControllerDriverMappingBasic)/sizeof(mPreciseControllerDriverMappingBasic[0]);
  }

  if (PcdGetBool (PcdCsmEnable)) {
    //
    // Install PlatformDriverOverride to let CSM driver control device
    //
    Handle = NULL;
    Status = gBS->InstallMultipleProtocolInterfaces (
                    &Handle,
                    &gEfiPlatformDriverOverrideProtocolGuid, &mPlatformOverride,
                    &gCsmLoaderProtocolGuid, NULL,
                    NULL
                    );
    ASSERT_EFI_ERROR (Status);
  }

  return EFI_SUCCESS;
}
